Enterprise Objects Framework Release 1.1 Copyright ã1995 by NeXT Computer, Inc.  All Rights Reserved.

EODatabaseChannel

Inherits From:	NSObject

Declared In:	eoaccess/EODatabaseChannel.h

Class Description

An EODatabaseChannel represents an independent communication channel to the database server its EODatabase object is connected to, and manipulates records as instances of enterprise object classes specified in its EODatabase's EOModel. It supports the standard select, fetch, insert, delete, and update operations, and can lock individual records.

An EODatabaseChannel works with an EODatabaseContext object, which manages transactions. All of a database channel's operations take place within the context of transactions controlled or tracked by the database context. A database context may work with several channels (if the adaptor permits it), but a channel is associated with only one context.

A database channel creates a EOAdaptorChannel when initialized, and uses this object to communicate with the database server.

Accessing the Database

An EODatabaseChannel operates on database rows in the form of enterprise objects. The database channel uses its adaptor channel to fetch rows in the form of dictionary objects, whose keys are attribute names and whose values are the values for those attributes. The database channel uses these dictionaries to create enterprise objects with the takeValuesFromDictionary: method of the EOKeyValueCoding protocol.

Once you've set up a suite of database objects and begun a transaction, you can have the database channel operate on the data in the server. To insert a new row for a particular entity, for example, you create an enterprise object containing all the values for the entity's attributes, and send the channel an insertObject: message. Similarly, you can delete a given enterprise object with the deleteObject: method, update it with updateObject:, and lock it with lockObject:. Locking an object prevents anyone else from updating it until the transaction it was locked in ends.

The remaining operationÐselectÐrequires an EOQualifier object to specify which objects to select. An EOQualifier describes enterprise objects based on the values of attributes; for example, objects whose salary attribute is greater than 40,000. See the EOQualifier class specification for more information on creating qualifiers.

Fetching rows with a database channel is a two-stage process. First, you select the rows with a selectObjectsDescribedByQualifier:fetchOrder: message. If this succeeds, you then send fetchWithZone: messages to retrieve individual enterprise objects until there are no more. This code template displays the highlights of the process, using an unrestricted qualifier to fetch all objects for employeeEntity:

EODatabaseChannel *myChannel;  /* Assume this exists and is open. */
EOEntity *employeeEntity;      /* Assume this exists. */
EOQualifier *myQualifier;
id theEmployee;
BOOL ok;

myQualifier = [employeeEntity qualifier];

[[myChannel databaseContext] beginTransaction];

ok = [myChannel selectObjectsDescribedByQualifier:myQualifier
                fetchOrder:nil];

if (ok) {
    while (theEmployee = [myChannel fetchWithZone:NULL]) {
       /* Process theEmployee. */
    }
}

[[myChannel databaseContext] commitTransaction];

Notifying the Database Channel's Delegate

All of the object-manipulation methods notify the database channel's delegate before and after performing their operations. The delegate need only implement a message to receive it. The delegate may respond to the ªbeforeº messages in such a way as to allow or disallow the operation, or to alter its result. The specific action available to the delegate depends on the message received.

One important delegate message is databaseChannel:failedToLookupClassNamed:. Since an EOEntity can be associated with an enterprise object class that isn't linked into the application, it's possible that the database channel won't find the class when it fetches the first object for that entity. In this case, it notifies its delegate, which is then responsible for finding and loading that class (typically from a bundle).

Another failure notification is databaseChannel:willRefetchConflictingObject:withSnapshot:. This is sent if you refetch an object after it's been updated, but before the transaction it was updated in has been committed. Suppose one channel updates an object and another then tries to fetch it. When the second channel fetches that object it gets the values the object had the last time it was fetched, because the update hasn't yet been committed. These values differ from the now-updated values of the enterprise object. A database channel won't make a decision about which of these two states is valid, so it leaves it to the delegate.

Working with the Adaptor Channel

EODatabaseChannel offers a great deal of power in operating on the objects whose data is stored in a database, using a high-level interface that doesn't assume much about that database. Sometimes you need to work at a lower level, thoughÐto access a server-specific feature, or to perform an operation that EODatabaseChannel's interface doesn't support well. In this case, you can get the database channel's adaptor channel and communicate more directly with the server, even to the extent of sending raw SQL.

You do this at the cost of database-dependence, and with the risk of altering the state of the database without the EODatabaseChannel (or other database-level objects) being notified. This isn't a problem if you finish your work with the adaptor channel and return things to an expected state, but when you mix operations across levels you have to explicitly synchronize the different objects with regard to changes in state. You can manage transaction state at the database level when working with the adaptor channel, but for other changes, you may have to do your work with the adaptor channel and then notify the database-level objects about any changes. Specifically, if your application sends raw SQL to the server with EOAdaptorChannel's evaluateExpression:, it must note all changes that take effect on the server and send the appropriate messages to the database level objects to keep them synchronized with those changes (they notify their adaptor level objects as needed).

Performing a select operation, in particular, requires special handling if you then intend to have the EODatabaseChannel do the fetching. If you use EOAdaptorChannel's selectAttributes:describedByQualifier:fetchOrder:lock: or evaluateExpression: messages to perform a select operation, the database channel doesn't know what kind of enterprise objects it should fetch. You must use the setCurrentEntity: method to tell it what entity it should fetch objects for; it can determine the rest of what it needs based on the adaptor channel's state. From the current entity the database channel can determine what enterprise object class to instantiate upon the next fetchWithZone: message.

Instance Variables

 None declared in this class.

Method Types

Initializing a new instance	-  initWithDatabaseContext:

Opening and closing a channel	- openChannel
- isOpen
- closeChannel

Modifying objects	- insertObject:
- deleteObject:
- lockObject:
- updateObject:

Fetching objects	- selectObjectsDescribedByQualifier:fetchOrder:
- fetchWithZone:
- isFetchInProgress
- refetchObject:
- cancelFetch
- setCurrentEntity:

Getting the adaptor channel	- adaptorChannel

Getting the database context	- databaseContext

Setting the delegate	- setDelegate:
- delegate

Instance Methods

adaptorChannel

- (EOAdaptorChannel *)adaptorChannel

Returns the EOAdaptorChannel used by the EODatabaseChannel for communication with the database server. See ªWorking with the Adaptor Channelº in the class description for information on safely using an EODatabaseChannel's adaptor channel.

cancelFetch

- (void)cancelFetch

Cancels any fetch in progress.

See also:  - isFetchInProgress, - selectObjectsDescribedByQualifier:fetchOrder:, - fetchWithZone:

closeChannel

- (void)closeChannel

Closes the EODatabaseChannel so that it can't perform operations with the server. Any fetch in progress is canceled. If the channel's database context has outstanding transactions, however, the result depends on the server; some database servers immediately roll back all outstanding transactions while others wait until the user logs out from the database.

See also:  - openChannel, - nestedTransactions (EODatabaseContext)

databaseContext

- (EODatabaseContext *)databaseContext

Returns the EODatabaseContext that controls transactions for the EODatabaseChannel.

delegate

- delegate

Returns the EODatabaseChannel's delegate.

deleteObject:

- (BOOL)deleteObject:anEO

Deletes the row in the database server corresponding to anEO. Returns YES if successful, NO if not. Some of the reasons deletion may fail are:

·	anEO is nil.
·	A primary key can't be determined for anEO (because no snapshot was recorded).
·	No record corresponding to anEO's primary key exists in the database.
·	The receiver's EODatabaseContext has no transaction in progress.
·	The database doesn't allow the deletion.
·	The receiver's delegate disallows deletion.

This method invokes the delegate methods databaseChannel:willDeleteObject: and databaseChannel:didDeleteObject:.

See also:  - isReadOnly (EOEntity)

fetchWithZone:

- fetchWithZone:(NSZone *)zone

Fetches and returns the next object in the result set produced by a selectObjectsDescribedByQualifier:fetchOrder: message; returns nil if there are no more objects in the current result set or if an error occurs. If the database channel's EODatabase performs uniquing and the object fetched already exists, it's simply sent the new values; otherwise the channel creates a new enterprise object.

To create a new enterprise object, the database channel allocates an instance of the enterprise object class for the entity being fetched. The channel then initializes the object with initWithPrimaryKey:entity: if it implements that message, otherwise simply with an init message. To set the object's values the channel sends it a takeValuesFromDictionary: message with the fetched row as the argument, and finishes off with an awakeForDatabaseChannel: message (described in the EODatabaseChannelNotification informal protocol specification).

If an enterprise object matching the next selected object has already been uniqued by the EODatabaseChannel's EODatabase, that object is sent takeValuesFromDictionary: and awakeForDatabaseChannel: messages to overwrite its values with those just fetched. If the uniqued object's values differ from those fetched, an ambiguity results; see the description of the databaseChannel:willRefetchConflictingObject:withSnapshot: delegate message for information on how such a situation is handled.

This method invokes the delegate methods databaseChannel:willFetchObjectOfClass:withZone: and databaseChannel:didFetchObject:, and may invoke either databaseChannel:willRefetchObject:fromSnapshot: or databaseChannel:willRefetchConflictingObject:withSnapshot:.

See also:  - objectForPrimaryKey:entity: (EODatabase and EODatabaseContext), - takeValuesFromDictionary: (EOKeyValueCoding informal protocol)

initWithDatabaseContext:

- initWithDatabaseContext:(EODatabaseContext *)aDatabaseContext

Initializes a newly allocated EODatabaseChannel with aDatabaseContext as the EODatabaseContext it works in. Doesn't automatically open the channel. The new EODatabaseChannel retains aDatabaseContext. This is the designated initializer for the EODatabaseChannel class. Returns self, or nil if no more channels can be associated with aDatabaseContext.

See also:  - openChannel

insertObject:

- (BOOL)insertObject:anEO

Attempts to insert anEO's simple (nonflattened, nonderived) attribute values as a row into the database server, returning YES if successful and NO if not. If anEO is successfully inserted, the receiver's database context uniques it and records a snapshot with recordObject:primaryKey:snapshot:. Raises NSInvalidArgumentException if anEO is a fault (see the EOFault class specification for information on faults).

Some of the reasons insertion may fail are:

·	anEO is nil.
·	The receiver's EODatabaseContext has no transaction in progress.
·	anEO's EOEntity has a read-only attribute that the server requires be set.
·	A primary key can't be determined for anEO.
·	The database doesn't allow insertion.
·	The receiver's delegate disallows insertion.

Note that read-only attributes are silently unmodified, but if the server requires such an attribute to be non-NULL an error will occur.

This method invokes the delegate methods databaseChannel:willInsertObject: and databaseChannel:didInsertObject:.

See also:  - isReadOnly (EOEntity)

isFetchInProgress

- (BOOL)isFetchInProgress

Returns YES if the EODatabaseChannel is fetching, NO otherwise. A database channel is fetching if it's been sent a successful selectObjectsDescribedByQualifier:fetchOrder: message. A database channel stops fetching when there are no more objects to fetch or when it's sent a cancelFetch message.

isOpen

- (BOOL)isOpen

Returns YES if the channel has been successfully opened with openChannel, NO if not.

lockObject:

- (BOOL)lockObject:anEO

Locks anEO for update. Fetches the properties composing anEO's snapshot and compares the fetched values with the values stored in the snapshot. Returns YES if all values are the same; fails and returns NO if any of the values differ. Also returns NO under the following conditions:

·	anEO's entity is read-only.
·	The channel's database context has no transaction in progress.
·	The database server or its adaptor doesn't support locking.

This method invokes the delegate methods databaseChannel:willLockObject: and databaseChannel:didLockObject:.

See also:  - isReadOnly (EOEntity)

openChannel

- (BOOL)openChannel

Puts the channel and both its context and database into a state where they are ready to perform operations. Returns YES on success and NO on failure. You shouldn't attempt to open an already open channel.

See also:  - closeChannel

refetchObject:

- (BOOL)refetchObject:anEO

Refetches the object for anEO's primary key from the database server. anEO is modified by this method. Returns YES on success, NO on failure for any reason (specifically, if there's a fetch in progress). May also raise NSInternalInconsistencyException under the conditions described for the delegate method databaseChannel:willRefetchConflictingObject:withSnapshot:.

Some possible reasons for failure are:

·	anEO is nil.
·	No snapshot for anEO exists.
·	The receiver's EODatabaseContext has no transaction in progress.
·	The database channel has a fetch in progress.

This method invokes the delegate methods databaseChannel:willRefetchObject: and databaseChannel:didRefetchObject:, and may invoke databaseChannel:willRefetchConflictingObject:withSnapshot:.

See also:  - isFetchInProgress, - takeValuesFromDictionary: (EOKeyValueCoding informal protocol)

selectObjectsDescribedByQualifier:fetchOrder:

- (BOOL)selectObjectsDescribedByQualifier:(EOQualifier *)aQualifier
fetchOrder:(NSArray *)fetchOrder

Selects objects that match aQualifier. The selected objects compose one or more result sets, each object of which will be returned by subsequent fetchWithZone: messages in the order prescribed by fetchOrder (an array of EOAttributeOrdering objects). fetchOrder can be nil, indicating no sorting of records. Returns YES on success, NO on failure for any reason. Some possible reasons for failure are:

·	aQualifier is nil or invalid.
·	The receiver's EODatabaseContext has no transaction in progress.
·	The receiver's delegate disallows the select.
·	The receiver's EOAdaptorChannel fails to perform the select operation.

This method invokes the delegate methods databaseChannel:willSelectObjectsDescribedByQualifier:fetchOrder: and databaseChannel:didSelectObjectsDescribedByQualifier:fetchOrder:.

See also:  - fetchWithZone:

setCurrentEntity:

- (void)setCurrentEntity:(EOEntity *)anEntity

Sets the entity used when fetching enterprise objects. Subsequent fetchWithZone: messages during a fetch operation create an object of the class associated with anEntity. If you perform a select operation in the database server with the database channel's adaptor channel, you should set the proper entity before having the EODatabaseChannel fetch any of the selected rows. See ªWorking with the Adaptor Channelº in the class description for an example.

This method is invoked automatically when you use selectObjectsDescribedByQualifier:fetchOrder:.

setDelegate:

- (void)setDelegate:anObject

Sets the EODatabaseChannel's delegate to anObject.

updateObject:

- (BOOL)updateObject:anEO

Updates the row in the database corresponding to anEO. Returns YES if successful, NO if not. Some possible reasons for failure are:

·	anEO is nil.
·	The receiver's EODatabaseContext has no transaction in progress.
·	A primary key can't be determined for anEO (because no snapshot was recorded).
·	No record for anEO's primary key exists in the database.
·	anEO's EOEntity is read-only.
·	anEO's EOEntity has a read-only attribute that the server requires be set.
·	The update strategy of the receiver's EODatabaseContext doesn't permit the update.
·	The receiver's delegate disallows the update.

Note that read-only attributes are silently unmodified, but if the server requires such an attribute to be non-NULL an error will occur.

This method invokes the delegate methods databaseChannel:willUpdateObject: and databaseChannel:didUpdateObject:.

See also:  - isReadOnly (EOEntity)

Methods Implemented by the Delegate

databaseChannel:didDeleteObject:

- (void)databaseChannel:channel didDeleteObject:anEO

Invoked after channel has deleted anEO from the database.

databaseChannel:didFetchObject:

- (void)databaseChannel:channel didFetchObject:anEO

Invoked after channel has fetched anEO from the database.

databaseChannel:didInsertObject:

- (void)databaseChannel:channel didInsertObject:anEO

Invoked after channel has inserted anEO into the database.

databaseChannel:didLockObject:

- (void)databaseChannel:channel didLockObject:anEO

Invoked after channel has locked anEO.

databaseChannel:didSelectObjectsDescribedByQualifier:fetchOrder:

- (void)databaseChannel:channel didSelectObjectsDescribedByQualifier:(EOQualifier *)aQualifier fetchOrder:(NSArray *)fetchOrder

Invoked after channel has selected objects described by aQualifier with fetchOrder.

databaseChannel:didUpdateObject:

- (void)databaseChannel:channel didUpdateObject:anEO

Invoked after channel has updated anEO in the database.

databaseChannel:failedToLookupClassNamed:

- (Class)databaseChannel:channel failedToLookupClassNamed:(const char *)name

Invoked when channel has failed to find a class with the given name in the run-time system. The delegate should load an external module if needed and return the named class. If the delegate returns nil or doesn't implement this method, then EOGenericRecord is used. 

See also:  - className (EOEntity)

databaseChannel:relationshipForRow:relationship:

- (EORelationship *)databaseChannel:channel
 relationshipForRow:(NSDictionary *)aRow
 relationship:(EORelationship *)aRelationship

Invoked when a relationship is instantiated on a newly fetched object. aRow contains the values for the object just fetched, and aRelationship is the relationship about to be set up. The delegate can use the information in aRow to determine which entity the destination of the relationship should be associated with, and return a substitute for aRelationship as needed.

This method allows you to reuse the same column in a table for different kinds of relationships based on other values for the object. For example, suppose you have one table containing two kinds of Employee entities, full- and part-time, each with their own kind of time card. You can use an isPartTime attribute to determine whether the relationship to a time card associates each employee object with the table for full-time cards or the table for part-time cards.

To set this up properly in your model, you define all the relationships you need, but mark only one of them in the Class Properties column of EOModeler's Entity Inspector. This causes a database to resolve only that relationship when it fetches objects, making it the default relationship from the set you define. You then use databaseChannel:relationshipForRow:relationship: at run time to substitute one of the other relationships. In this example, toFullTimeCard and toPartTimeCard are defined in the model, and toFullTimeCard is the default relationship:

- (EORelationship *)databaseChannel:channel
    relationshipForRow:(NSDictionary *)aRow
    relationship:(EORelationship *)aRelationship
{
    EORelationship *toFullTimeCard;  /* Assume this exists. */
    EORelationship *toPartTimeCard;  /* Assume this exists. */
    NSNumber *isPartTime;

    if (aRelationship == toFullTimeCard) {
        isPartTime = [aRow objectForKey:@"isPartTime"];
        if ([isPartTime intValue] != 0) {
            return toPartTimeCard;
        }
        return toFullTimeCard;
    }

    return aRelationship;
}

This method first checks that the relationship being resolved is toFullTimeCard, and if so, examines the value for the ªisPartTimeº key in the fetched row to determine which relationship to substitute. If the value is nonzero, then toPartTimeCard is used instead of toFullTimeCard, giving the employee object a part-time card object. Similarly, if the value for the isPartTime attribute is 0, toFullTimeCard is used, so that the employee object gets a a full-time card object.

databaseChannel:willDeleteObject:

- databaseChannel:channel willDeleteObject:anEO

Invoked before channel deletes anEO. The delegate may return anEO, a substitute object to delete, or nil to prevent the deletion.

databaseChannel:willFetchObjectOfClass:withZone:

- (void)databaseChannel:channel
willFetchObjectOfClass:(Class)class
withZone:(NSZone *)zone

Invoked before channel fetches an object.

databaseChannel:willInsertObject:

- databaseChannel:channel willInsertObject:anEO

Invoked before channel inserts anEO. The delegate may return a substitute object to insert or nil to disallow the insertion.

databaseChannel:willLockObject:

- (BOOL)databaseChannel:channel willLockObject:anEO

Invoked before channel locks anEO. The delegate may return YES to allow anEO to be locked or NO to disallow it.

databaseChannel:willRefetchConflictingObject:withShapshot:

- (NSDictionary *)databaseChannel:channel
willRefetchConflictingObject:anEO
withShapshot:(NSDictionary *)snapshot

Invoked when channel has fetched new values for anEO, but anEO's current values differ from its existing snapshot. This happens when anEO is updated during a transaction that hasn't been committed. In such a situation the object has two potential states, and the database channel can't determine which is valid; this usually indicates a programming error which requires intervention by the delegate. See ªNotifying the Database Channel's Delegateº in the class description for more information.

If the delegate doesn't implement this method the database channel raises NSInternalInconsistencyException.

databaseChannel:willRefetchObject:fromSnapshot:

- (NSDictionary *)databaseChannel:channel
willRefetchObject:anEO
fromSnapshot:(NSDictionary *)aSnapshot

Invoked before channel refreshes anEO with the values in aSnapshot just fetched from the database. The delegate may return aSnapshot, a substitute snapshot, or nil to disallow the operation.

databaseChannel:willSelectObjectsDescribedByQualifier:fetchOrder:

- (BOOL)databaseChannel:channel willSelectObjectsDescribedByQualifier:(EOQualifier *)aQualifier fetchOrder:(NSArray *)fetchOrder

Invoked before channel performs a select operation. The delegate shouldn't modify aQualifier or fetchOrder. If the delegate returns YES the select proceeds; if the delegate returns NO the select operation is aborted.

databaseChannel:willUpdateObject:

- databaseChannel:channel willUpdateObject:anEO

Invoked before channel updates anEO. The delegate may return anEO, a substitute object to update, or nil to disallow the update.